package com.virtual.common.utils;

import android.content.Context;
import android.content.res.AssetManager;
import android.os.Build;
import android.os.Parcel;
import android.system.Os;
import android.text.TextUtils;

import java.io.BufferedOutputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.Closeable;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.RandomAccessFile;
import java.net.PasswordAuthentication;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.channels.Channels;
import java.nio.channels.FileChannel;
import java.nio.channels.ReadableByteChannel;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

public class FileUtils {

  public static void AssetsFileToPath(Context context, String fileName,
      String path) {
    InputStream is = null;
    OutputStream os = null;
    AssetManager assetManager = context.getAssets();
    try {

      is = assetManager.open(fileName);
      os = new FileOutputStream(path);
      int length;
      byte[] buffer = new byte[1024];
      while ((length = is.read(buffer)) > 0) {
        os.write(buffer, 0, length);
      }
    } catch (IOException e) {
      e.printStackTrace();
    } finally {
      try {
        if (is != null) {
          is.close();
        }
        if (os != null) {
          os.close();
        }
      } catch (IOException e) {
        e.printStackTrace();
      }

    }
  }

  public static void chmod(String path, int mode) throws Exception {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
      try {
        Os.chmod(path, mode);
        return;
      } catch (Exception e) {
      }
    }
    File file = new File(path);
    String cmd = "chmod ";
    if (file.isDirectory()) {
      cmd += " -R ";
    }
    String cmode = String.format("%o", mode);
    Runtime.getRuntime().exec(cmd + cmode + " " + path).waitFor();
  }

  public static void createSymlink(String oldPath, String newPath) throws Exception {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
      Os.symlink(oldPath, newPath);
    } else {
      Runtime.getRuntime().exec("ln -s " + oldPath + " " + newPath).waitFor();
    }
  }

  public static boolean isSymlink(File file) throws IOException {
    if (file == null) {
      throw new NullPointerException("File must not be null");
    }
    File canon;
    if (file.getParent() == null) {
      canon = file;
    } else {
      File canonDir = file.getParentFile().getCanonicalFile();
      canon = new File(canonDir, file.getName());
    }
    return !canon.getCanonicalFile().equals(canon.getAbsoluteFile());
  }

  public static void writeParcelToFile(Parcel p, File file) throws IOException {
    FileOutputStream fos = new FileOutputStream(file);
    fos.write(p.marshall());
    fos.close();
  }

  public static byte[] toByteArray(InputStream inStream) throws IOException {
    ByteArrayOutputStream swapStream = new ByteArrayOutputStream();
    byte[] buff = new byte[100];
    int rc;
    while ((rc = inStream.read(buff, 0, 100)) > 0) {
      swapStream.write(buff, 0, rc);
    }
    return swapStream.toByteArray();
  }

  public static boolean deleteDir(File dir) {
    if (dir.isDirectory()) {
      String[] children = dir.list();
      for (String file : children) {
        boolean success = deleteDir(new File(dir, file));
        if (!success) {
          return false;
        }
      }
    }
    return dir.delete();
  }

  public static boolean deleteDir(String dir) {
    return deleteDir(new File(dir));
  }

  public static void writeToFile(InputStream dataIns, File target) throws IOException {
    final int BUFFER = 1024;
    BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(target));
    int count;
    byte data[] = new byte[BUFFER];
    while ((count = dataIns.read(data, 0, BUFFER)) != -1) {
      bos.write(data, 0, count);
    }
    bos.close();
  }

  public static void writeToFile(byte[] data, File target) throws IOException {
    FileOutputStream fo = null;
    ReadableByteChannel src = null;
    FileChannel out = null;
    try {
      src = Channels.newChannel(new ByteArrayInputStream(data));
      fo = new FileOutputStream(target);
      out = fo.getChannel();
      out.transferFrom(src, 0, data.length);
    } finally {
      if (fo != null) {
        fo.close();
      }
      if (src != null) {
        src.close();
      }
      if (out != null) {
        out.close();
      }
    }
  }

  public static void copyFile(File source, File target) throws IOException {
    FileInputStream inputStream = null;
    FileOutputStream outputStream = null;
    try {
      inputStream = new FileInputStream(source);
      outputStream = new FileOutputStream(target);
      FileChannel iChannel = inputStream.getChannel();
      FileChannel oChannel = outputStream.getChannel();
      ByteBuffer buffer = ByteBuffer.allocate(1024);
      while (true) {
        buffer.clear();
        int r = iChannel.read(buffer);
        if (r == -1) {
          break;
        }
        buffer.limit(buffer.position());
        buffer.position(0);
        oChannel.write(buffer);
      }
    } finally {
      closeQuietly(inputStream);
      closeQuietly(outputStream);
    }
  }

  public static void closeQuietly(Closeable closeable) {
    if (closeable != null) {
      try {
        closeable.close();
      } catch (Exception ignored) {
      }
    }
  }

  public static int peekInt(byte[] bytes, int value, ByteOrder endian) {
    int v2;
    int v0;
    if (endian == ByteOrder.BIG_ENDIAN) {
      v0 = value + 1;
      v2 = v0 + 1;
      v0 = (bytes[v0] & 255) << 16 | (bytes[value] & 255) << 24 | (bytes[v2] & 255) << 8 | bytes[v2 + 1] & 255;
    } else {
      v0 = value + 1;
      v2 = v0 + 1;
      v0 = (bytes[v0] & 255) << 8 | bytes[value] & 255 | (bytes[v2] & 255) << 16 | (bytes[v2 + 1] & 255) << 24;
    }
    return v0;
  }

  private static boolean isValidExtFilenameChar(char c) {
    switch (c) {
      case '\0':
      case '/':
        return false;
      default:
        return true;
    }
  }

  public static boolean isValidExtFilename(String name) {
    return (name != null) && name.equals(buildValidExtFilename(name));
  }

  public static String buildValidExtFilename(String name) {
    if (TextUtils.isEmpty(name) || ".".equals(name) || "..".equals(name)) {
      return "(invalid)";
    }
    final StringBuilder res = new StringBuilder(name.length());
    for (int i = 0; i < name.length(); i++) {
      final char c = name.charAt(i);
      if (isValidExtFilenameChar(c)) {
        res.append(c);
      } else {
        res.append('_');
      }
    }
    return res.toString();
  }

  public static String createFileDir(String absolutePath) {
    File file = new File(absolutePath);
    if (file.exists()) {
      return file.getAbsolutePath();
    }
    file.mkdirs();
    return file.getAbsolutePath();
  }

  public interface FileMode {
    int MODE_ISUID = 04000;
    int MODE_ISGID = 02000;
    int MODE_ISVTX = 01000;
    int MODE_IRUSR = 00400;
    int MODE_IWUSR = 00200;
    int MODE_IXUSR = 00100;
    int MODE_IRGRP = 00040;
    int MODE_IWGRP = 00020;
    int MODE_IXGRP = 00010;
    int MODE_IROTH = 00004;
    int MODE_IWOTH = 00002;
    int MODE_IXOTH = 00001;
    int MODE_755 = MODE_IRUSR | MODE_IWUSR | MODE_IXUSR
        | MODE_IRGRP | MODE_IXGRP
        | MODE_IROTH | MODE_IXOTH;
  }

  public static class FileLock {
    private static FileLock singleton;
    private Map<String, FileLockCount> mRefCountMap = new ConcurrentHashMap<String, FileLockCount>();

    public static FileLock getInstance() {
      if (singleton == null) {
        singleton = new FileLock();
      }
      return singleton;
    }

    private int RefCntInc(String filePath, java.nio.channels.FileLock fileLock, RandomAccessFile randomAccessFile,
        FileChannel fileChannel) {
      int refCount;
      if (this.mRefCountMap.containsKey(filePath)) {
        FileLockCount fileLockCount = this.mRefCountMap.get(filePath);
        int i = fileLockCount.mRefCount;
        fileLockCount.mRefCount = i + 1;
        refCount = i;
      } else {
        refCount = 1;
        this.mRefCountMap.put(filePath, new FileLockCount(fileLock, refCount, randomAccessFile, fileChannel));
      }
      return refCount;
    }

    private int RefCntDec(String filePath) {
      int refCount = 0;
      if (this.mRefCountMap.containsKey(filePath)) {
        FileLockCount fileLockCount = this.mRefCountMap.get(filePath);
        int i = fileLockCount.mRefCount - 1;
        fileLockCount.mRefCount = i;
        refCount = i;
        if (refCount <= 0) {
          this.mRefCountMap.remove(filePath);
        }
      }
      return refCount;
    }

    public boolean LockExclusive(File targetFile) {
      if (targetFile == null) {
        return false;
      }
      try {
        File lockFile = new File(targetFile.getParentFile().getAbsolutePath().concat("/lock"));
        if (!lockFile.exists()) {
          lockFile.createNewFile();
        }
        RandomAccessFile randomAccessFile = new RandomAccessFile(lockFile.getAbsolutePath(), "rw");
        FileChannel channel = randomAccessFile.getChannel();
        java.nio.channels.FileLock lock = channel.lock();
        if (!lock.isValid()) {
          return false;
        }
        RefCntInc(lockFile.getAbsolutePath(), lock, randomAccessFile, channel);
        return true;
      } catch (Exception e) {
        return false;
      }
    }

    public void unLock(File targetFile) {
      File lockFile = new File(targetFile.getParentFile().getAbsolutePath().concat("/lock"));
      if (!lockFile.exists()) {
        return;
      }
      if (this.mRefCountMap.containsKey(lockFile.getAbsolutePath())) {
        FileLockCount fileLockCount = this.mRefCountMap.get(lockFile.getAbsolutePath());
        if (fileLockCount != null) {
          java.nio.channels.FileLock fileLock = fileLockCount.mFileLock;
          RandomAccessFile randomAccessFile = fileLockCount.fOs;
          FileChannel fileChannel = fileLockCount.fChannel;
          try {
            if (RefCntDec(lockFile.getAbsolutePath()) <= 0) {
              if (fileLock != null && fileLock.isValid()) {
                fileLock.release();
              }
              if (randomAccessFile != null) {
                randomAccessFile.close();
              }
              if (fileChannel != null) {
                fileChannel.close();
              }
            }
          } catch (IOException e) {
            e.printStackTrace();
          }
        }
      }
    }

    private class FileLockCount {
      FileChannel fChannel;
      RandomAccessFile fOs;
      java.nio.channels.FileLock mFileLock;
      int mRefCount;

      FileLockCount(java.nio.channels.FileLock fileLock, int mRefCount, RandomAccessFile fOs,
          FileChannel fChannel) {
        this.mFileLock = fileLock;
        this.mRefCount = mRefCount;
        this.fOs = fOs;
        this.fChannel = fChannel;
      }
    }
  }
}
